[auto-rotation feature] feat: add key auto-rotation specification + manual rotation for all key types#968
[auto-rotation feature] feat: add key auto-rotation specification + manual rotation for all key types#968Manuthor wants to merge 9 commits into
Conversation
fa12cc6 to
5f27e21
Compare
There was a problem hiding this comment.
Pull request overview
This PR is the first in the key auto-rotation feature stack. It introduces the canonical auto-rotation specification document and lands the underlying manual rotation implementation for keys and certificates (ReKey, ReKeyKeyPair, ReCertify), plus the database plumbing and test vectors needed for wrapping-key dependant rewrites and certificate renewal link chains.
Changes:
- Add comprehensive documentation for rotation policy attributes, scheduler semantics, and rotation/renewal scenarios (with diagrams and attribute tables).
- Refactor and extend server KMIP operations to implement manual rotation flows for symmetric keys, key pairs, and certificate renewal (
ReCertify) using a shared orchestration trait. - Add
ObjectsStore::find_wrapped_by()across SQL backends to support wrapping-key rotations that must re-wrap dependants, and extend test vectors/runner docs accordingly.
Reviewed changes
Copilot reviewed 35 out of 35 changed files in this pull request and generated 16 comments.
Show a summary per file
| File | Description |
|---|---|
| README.md | Adds a documentation link for scheduled key auto-rotation. |
| documentation/mkdocs.yml | Registers the new Key Auto-Rotation documentation page in the nav. |
| documentation/docs/kmip_support/key_auto_rotation.md | Adds the full auto-rotation specification and scenarios (policy attributes, flows, roadmap). |
| crate/test_kms_server/src/vector_runner.rs | Registers new negative and positive vectors for ReCertify and offset state verification. |
| crate/test_kms_server/README.md | Updates vector counts and documents newly added vectors. |
| crate/server/src/core/wrapping/wrap.rs | Tightens self-wrap handling and bypasses KEK ownership checks for server-wide KEK. |
| crate/server/src/core/operations/rekey/* | Introduces a new rekey/ module with shared orchestration + symmetric/keypair flows. |
| crate/server/src/core/operations/recertify.rs | Adds server-side ReCertify implementation and dependant relinking behavior. |
| crate/server/src/core/operations/{mod.rs,message.rs,dispatch.rs} | Wires ReCertify through dispatch and operation processing. |
| crate/server/src/core/operations/key_ops/mod.rs | Fixes lifecycle setup so PreActive objects retain a future activation_date. |
| crate/server/src/core/operations/certify/* | Broadens visibility of certify helpers/types for reuse by ReCertify. |
| crate/server/src/core/kms/kmip.rs | Adds the KMS::recertify entry point. |
| crate/interfaces/src/stores/objects_store.rs | Adds find_wrapped_by() to the store trait (default empty). |
| crate/server_database/src/core/database_objects.rs | Exposes Database::find_wrapped_by() across backends. |
| crate/server_database/src/stores/sql/{sqlite,pgsql,mysql}.rs | Implements find_wrapped_by() using JSON queries in each SQL backend. |
| crate/kmip/src/kmip_2_1/{kmip_operations,kmip_messages}.rs | Adds KMIP 2.1 ReCertify request/response types + message (de)serialization. |
| crate/kmip/src/kmip_1_4/kmip_operations.rs | Adds 1.4↔2.1 conversions for ReCertify types and operation mapping. |
| CHANGELOG/feat_key-rotation-manual.md | Adds the required branch-specific changelog for manual rotation changes. |
| CHANGELOG/docs_key-autorotation-spec.md | Adds the required branch-specific changelog for the spec doc addition. |
291bfbd to
1b09922
Compare
tbrezot
left a comment
There was a problem hiding this comment.
I could not go through all diffs, but thanks to your documentation I could grasp enough of the idea to have a few questions (cf my comment of the doc). I will complete the review once we have discussed those points.
There was a problem hiding this comment.
Great design documentat!
Auto-rotation is an extremely complex topic (as in "it touches everything and thus impacts everything"). After reading this document I am left wondering with a few things:
-
it seems useful to give the set of all rotations of a given key an identity (in Google's terms, this is a key-set, we could also call it key-chain to be more explicit about its structure; I will be using key-set in what follows), since from the user point of view, all the keys generated by auto-rotation are actually the same key. In a first approximation, it seems the user should not have to know more than that, which brings up the key selection issue.
-
It seems needed to be able to:
- encrypt with a key-set (which uses its latest key)
- encrypt with a specific key. We have the IDs, but since they are automatically and randomly generated, I don't think they can be of much use. We could instead have a special syntax to describe the version of the key to use in a key-set (e.g.
key-set-name@version) - decrypt with a key-set (which tries each key in the key-set until it can successful decrypt)
- decrypt with a specific key (same as encrypt)
- decrypt with a key-set with a temporal hint (for example if we know the creation date of a ciphertext there is no need to attempt decrypting it with newer keys in the key-set).
In Covercrypt we internally use similar techniques for key-rotation: each right is associated to a key-set which is implemented as a singly-linked list (why do you need a doubly-linked list in the KMS? It seems we never want to traverse it in chronological order but always in anti-chronological order when attempting to decrypt a document). We therefore have a key-set ID (the right), and each key in the set can be selected by its distance to the latest (the depth of the smallest prefix of the chain containing it).
-
it is never made explicit here but it seems natural to prevent rotating keys that are not activated or expired.
There was a problem hiding this comment.
It seems useful to give the set of all rotations a key-set identity.
Agree. Our current design uses the x-rotate-name vendor attribute as the key-set identity. All rotations of a key share the same x-rotate-name value, which serves as the stable identifier the user interacts with.
Let me paraphrase just to be sure I've understood:
- Encrypt with key-set →
Locatebyx-rotate-name+x-rotate-latest=true→ returns the current active key →Encrypt - Encrypt with specific version → Use the key's UID directly (or
x-rotate-name+x-rotate-generation=N) - Decrypt with key-set → Client-side: try latest first, walk
ReplacedObjectLinkchain backward (or server could try each key — not yet implemented) - Decrypt with temporal hint → Use creation-date metadata to narrow the candidate set
The key-set-name@version syntax is a great suggestion. We could support it as a UID alias in the Locate / operation path.
It seems we never want to traverse it in chronological order but always in anti-chronological order when attempting to decrypt.
Answer: The doubly-linked list (both ReplacedObjectLink and ReplacementObjectLink) is mandated by KMIP 2.1 §6.1.46 (Re-key) and §11.28 (Link Type Enumeration), Table 464:
Replacement Object Link(value00000106) on the old key (points forward to the new key)Replaced Object Link(value00000107) on the new key (points backward to the old key)
Use cases for forward traversal:
- Admin cleanup: "find all successors of this key and revoke/destroy them" (e.g., security incident response)
- Audit trail: walk the chain forward to show the full rotation history
- Migration: when upgrading a key to a new algorithm, walk forward to find the latest version
In practice, decryption indeed only needs backward traversal (newest → oldest). But the KMIP spec requires both directions, and both are cheap to maintain (one link per key).
It seems natural to prevent rotating keys that are not activated or expired.
Answer: Agreed and partially implemented:
- ✅ Deactivated keys → error
- ✅ Destroyed keys → error (can't even retrieve)
⚠️ PreActive keys → currently allowed (debatable — a PreActive key hasn't been used yet, so rotation might be premature)⚠️ Compromised keys → currently allowed (might want to prevent rotation of compromised keys to avoid confusion)
There was a problem hiding this comment.
Thanks for your answer. Your rephrase is correct. Thanks for pointing out the KMIP requirements (since otherwise the use-cases you mention seem to work by walking the chain backward from the latest key of the key-set that should always be "locatable" given the owm of a given key in the key-set).
About forbidding rotation of non-active keys, I was talking about auto-rotation. But even then it is unclear what is the sensible thing to do and would require more discussion imho.
Related to that is the interaction of rotation with the key-set view of the system: what happens if the user rotates an older version of a key? Does it fork the chain? Does it truncate it? Imho rotation should always target key-set (or equivalently, manual rotation of keys that are not marked as latest should not be authorized)
9eb009a to
854aa02
Compare
5911dc5 to
dcc5aa5
Compare
tbrezot
left a comment
There was a problem hiding this comment.
A lot of effort has been put into this MR, and it greatly improves the initial implementation. I still have a few comments though:
- a few nitpicks that are trivial to correct;
- maybe an issue concerning the key-selection procedure;
- some deeper concerns about concurrency management, which are orthogonal to this feature and may be addressed in a dedicated MR.
Also, since it is structuring, it might gain from requesting a review from someone else. In any case, I may spend more time on it if you delay the merge (since it is huge, I could not read everything).
d7c575c to
f610a78
Compare
…pleteness
- server/src/core/operations/{recertify,revoke}.rs: wrap
retrieve_object_for_operation and revoke_key_core calls with
Box::pin to suppress clippy::large_futures.
- routes/crypto/{decrypt,encrypt,sign,unwrap}.rs: same Box::pin
treatment for all retrieve_object_for_operation call sites.
- interfaces/src/hsm/hsm_store.rs: add missing set_key_dates and
set_key_label methods to the mockall! test double so it matches
the updated HSM trait.
- server/src/core/uid_utils.rs: rename second mod tests to
mod hsm_tests to eliminate the 'name defined multiple times' error.
- server_database/src/stores/sql/sqlite.rs: replace undefined
SQLITE_QUERIES with PGSQL_QUERIES in the regression-guard test
(SQLite uses PGSQL_QUERIES / query.sql via get_sqlite_query!).
- nix/expected-hashes/{server.vendor.static,ui.vendor.non-fips}.sha256:
update hashes after thiserror 2.0.17→2.0.18 Cargo.lock bump.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The WASM non-fips vendor tarball hash changed after dependencies were updated. Update to sha256-C/9J14QduLLtw4KtWmxOgLAZKUvEhSnsMu5rF5PZS/A=. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add pin_fn_depth macro variant in dispatch for functions returning (Resp, Option<u32>) - Switch MACVerify dispatch arm to pin_fn_depth to unpack mac_verify tuple return - Remove unused kms.message() wrapper (route calls operations::message() directly) - Remove unused RequestMessage/ResponseMessage imports in kms/kmip.rs - Fix google_cse encrypt/decrypt callers to unpack (Resp, Option<u32>) tuples - Fix kmip.rs single-op dispatch path (dispatch() returns KResult<Operation>, not tuple) - Fix ui.vendor.non-fips.sha256 Nix hash Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The method was removed but is called by 6+ test files in crate/server/src/tests/. Restore it with #[allow(dead_code)] since it is only called from cfg(test) code and cargo build (without --tests) cannot see those callers. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
ckms subprocess cannot negotiate ML-DSA-44 TLS handshake without calling init_openssl_providers() at startup. Mark as ignored until the ckms binary is updated to support PQC TLS client connections. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
f610a78 to
7b7cbd4
Compare
Summary
Delivers the complete key auto-rotation specification and the full manual-rotation
implementation for all key types.
This is PR 1 of 4 in the key auto-rotation feature stack:
What's included
Documentation
x-rotate-interval,x-rotate-name,x-rotate-offset,x-rotate-generation,x-rotate-date)wrapped key, asymmetric key pair, wrapped private key (CoverCrypt),
certificate renewal (
ReCertify), server-wide KEKReplacementObjectLink/ReplacedObjectLink)Implementation —
Re-Key,Re-Key Key Pair,Re-CertifyComplete manual-rotation implementation for all six scenarios:
(RSA, EC, ML-KEM, ML-DSA, SLH-DSA, X25519, secp256k1, CoverCrypt)
Implements
ReCertify(KMIP 2.1 §6.1.45) for certificate renewal:ReplacedObjectLink/ReplacementObjectLink)PreActivestate for future-activation certificatesAll 344 test vectors pass.
Breaking changes
None.
Reviewer notes
This document is the canonical reference for all subsequent PRs in this stack.
Please review terminology and attribute semantics carefully; changes here will
cascade to the implementation PRs.